import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.StringTokenizer;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.text.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

/**
 * This class is used for recognizing the keywords and the comments.
 * After they are recognized, they will change color. Furthermore,the
 * program can change the background color of the specific line. 
 * some of the annotations are added by WANG Jiechao
 * @author Wang Feng
 * 
 */
public class TextEditor extends JTextPane implements KeyListener {

	private StyleContext m_context;//a container to maintain attributes of colors and allowed them to be reused
	private DefaultStyledDocument m_doc;//a document which can mark characters in the form of rich text format
	private MutableAttributeSet keyAttr, keyAttr1, normalAttr, commentAttr,
			lightAttr, removeLightAttr;//mutable attribute set
	private int num;
	public String pattern = "\n";
	private String textInput;
	static JTextPane lineNumberColumn;
	static int i,n;
	static JButton move=new JButton("move down");//this button seems not been used in this program
	static JButton moveto = new JButton("move to");//this button seems not been used in this program
	static final JTextField moveNum=new JTextField(3);	
	static int l=0,lineEnd;
    boolean textChange=false;
    static int enterAddress[]=new int[10000];
    boolean rightNum;
    String temp;
	// define two types of keywords
	private String[] keyWord1 = { "NOP", "SET", "CLR", "CPL", "XCSD", "SFA",
			"LAF", "MOV", "ANL", "ORL", "XRL", "RL", "RLC", "RR", "RRC", "INC",
			"DEC", "INC", "DEC", "ADD", "ADDC", "SUB", "SUBB", "PJMP", "PCALL",
			"DJNZ", "CJNE", "LJMP", "LCALL", "RET", "RETI", "SJMP", "JMP",
			"JZ", "JNZ", "JC", "JNC", "JPO", "JPE", "JS", "JNS", "PUSH", "POP",
			"MUL", "DIV", "DA", "IN", "OUT", "INT", "HLT",
                        "nop", "set", "clr", "cpl", "xcsd", "sfa",
			"laf", "mov", "anl", "orl", "xrl", "rl", "rlc", "rr", "rrc", "inc",
			"dec", "inc", "dec", "add", "addc", "sub", "subb", "pjmp", "pcall",
			"djnz", "cjne", "ljmp", "lcall", "ret", "reti", "sjmp", "jmp",
			"jz", "jnz", "jc", "jnc", "jpo", "jpe", "js", "jns", "push", "pop",
			"mul", "div", "da", "in", "out", "int", "hlt" };
	private String[] keyword2 = { "EQU", "ORG", "DB", "DS", "CSEG", "SEGMENT",
			"RSEG", "PUBLIC", "EXTERN","equ", "org", "db", "ds", "cseg", "segment",
			"rseg", "public", "extern" };
	private ArrayList<Integer> enterOffset = new ArrayList<Integer>();

	/**
	 * this is the constructor of the class
	 */

	public TextEditor() {

		super();
		Color initialColor = this.getBackground();//get background color
		m_context = new StyleContext();
		m_doc = new DefaultStyledDocument(m_context);
		this.setDocument(m_doc);// connect the text content and m_doc document
		this.addKeyListener(this);// listen the key event at the JTextpane
		
		
        lineNumberColumn = new JTextPane();
		
		lineNumberColumn.setBackground(Color.ORANGE);//set the background color
		lineNumberColumn.setFocusable(false);//set the state of focus to false	
			
		
		//set the line number showed before user type anything
		for (  i = 1; i <= 40; i++ ){
		  	   lineNumberColumn.setText(lineNumberColumn.getText()+ ""+ i + "\n" );
		}
		
		this.getDocument( ).addDocumentListener( new DocumentListener( ) {
			
	         public void insertUpdate( DocumentEvent e ) {//return a notification when user inserts something
	        	 textChange=true;
	        	Element root = m_doc.getDefaultRootElement();//element is an interface describing a piece of a document
	            num=root.getElementCount();//get number of the appeared lines 
	            for (  ; i <= num; i++ ){//set all the following line number when user type and type...
	               lineNumberColumn.setText(lineNumberColumn.getText()+ ""+ i + "\n" );
	            }
	            
	            
	         }
	             
	         public void  removeUpdate( DocumentEvent e ) {//return a notification when user remove something
	         textChange=true;
	         insertUpdate( e ); 
	         }
	         public void  changedUpdate( DocumentEvent e ) {//return a notification when attribute or attribute set is changed 
	        	 textChange=true;   
	        }
	      } 
		);
		
		move.addActionListener(new ActionListener(){//currently not been used in this program
	          public void actionPerformed(ActionEvent e){
	        	  try{
	        	  setCaretPosition(enterOffset.get(l));//set the position of the text insertion caret
	        	  
	        	  //when it's the first time to press the button"move down", there is no need to clean the color of the last line
	        	  if(l!=0)
	        	  m_doc.setCharacterAttributes(enterOffset.get(l-1),enterOffset.get(l)-enterOffset.get(l-1), removeLightAttr, true);
	        	   
	        	   m_doc.setCharacterAttributes(enterOffset.get(l),enterOffset.get(l+1)-enterOffset.get(l),lightAttr,true);           
	        	   
	        	             
	        	   l++;
                  	        	 
	          }catch (IndexOutOfBoundsException ex) {
	  			ex.printStackTrace();
	  			JOptionPane.showMessageDialog(TextEditor.this,"This is the last line!"); 
	  		}
	          }
	    }
		);
		
		moveto.addActionListener(new ActionListener(){//currently not been used in this program
		   	  public void actionPerformed(ActionEvent e){
		   	try{
		   		rightNum=true;
		   		temp=moveNum.getText();

		   		for (int i =0; i<temp.length();i++){
					if (!Character.isDigit(temp.charAt(i)))  //if any character is not digit						
						rightNum=false;							
		   		}
		   		if(!rightNum)
						JOptionPane.showMessageDialog(TextEditor.this,"Please input integer lager than 0!"); 
		   		
		   		if(rightNum){
		   		  n= Integer.parseInt(moveNum.getText());

		   		  if(n!=-1){
		   			   if(enterOffset.size()==n){
		   				   enterOffset.add(n,lineEnd);
		   			   }
		   			   
			   		   setCaretPosition(enterOffset.get(n));
			   		   m_doc.setCharacterAttributes(0,1000000000,removeLightAttr,true);
			   		   m_doc.setCharacterAttributes(enterOffset.get(n-1),enterOffset.get(n)-enterOffset.get(n-1),lightAttr,true);
		   		  } 
		   		}
		   	  }catch (IndexOutOfBoundsException ex) {
					ex.printStackTrace();
					JOptionPane.showMessageDialog(TextEditor.this,"You can only move to previous line!"); 
			   }
		   	   catch (NumberFormatException ex){
		   		    JOptionPane.showMessageDialog(TextEditor.this,"Please input integer lager than 0!"); 
		   	   }
		   	   catch (IllegalArgumentException ex){
	   		        JOptionPane.showMessageDialog(TextEditor.this,"You can only move to previous line!"); 
		   	   }     
		   		    
		   	   
		   	  }
		   	  }
		);
		
		//define colors
	    Color colorKeyword1=new Color(0,0,255);
	    Color colorKeyword2=new Color(160,0,160);
	    Color colorComment=new Color(200,200,0);
	    

		// define the attribute of the keywords
		keyAttr = new SimpleAttributeSet();
		StyleConstants.setForeground(keyAttr, colorKeyword1);

		keyAttr1 = new SimpleAttributeSet();
		StyleConstants.setForeground(keyAttr1, colorKeyword2);

		// define the attribute of the normal words
		//normalAttr = new SimpleAttributeSet();
		//StyleConstants.setForeground(normalAttr, Color.black);

		// define the attribute of the comment(following ";"): yellow color
		commentAttr = new SimpleAttributeSet();
		StyleConstants.setForeground(commentAttr, colorComment);

		// define the background color
		lightAttr = new SimpleAttributeSet();
		StyleConstants.setBackground(lightAttr, Color.lightGray);

		//define the attribute of initial color
		removeLightAttr = new SimpleAttributeSet();
		StyleConstants.setBackground(removeLightAttr, initialColor);

	}

	/**
	 * This method is used for getting the enterOffset of the class
	 * 
	 * @return ArrayList<Integer>
	 */

	public ArrayList<Integer> getEnterOffset() {
		return this.enterOffset;
	}

	/**
	 * This method is used for getting the number of lines
	 * 
	 * @return int
	 */

	public int getNum() {
		return this.num;
	}

	/**
	 * This method is used for analyzing the input and change the attribute of
	 * the words.If the user input something, it will analyze from the beginning
	 */

	public void syntaxParse() {
		try {
			String s = null;
			Element root = m_doc.getDefaultRootElement();//Gets the default root element
			// the whole text input is regarded as root 

			textInput = m_doc.getText(0, m_doc.getLength());
			
			num = root.getElementCount();//get the number of element in root

			int pos = 0;
			enterOffset.clear();
			enterOffset.add(0, 0);
			// this loop is used for getting the offset of the every enter
			for (int m = 0; m < num; m++) {
				if (textInput.indexOf(pattern, pos) >= 0) {
					enterOffset.add(textInput.indexOf(pattern, pos));
					pos = textInput.indexOf(pattern, pos) + pattern.length();
				}
			}

		
			int k=0;
			
            while(k<num){
			int i;
			int xStart = 0; 
			
			Element paraline = root.getElement(k);
			int lineStart = paraline.getStartOffset();// the start position of the line
			lineEnd = paraline.getEndOffset();
			s=m_doc.getText(lineStart, lineEnd-lineStart-1);
	        //System.out.println(k);
			if (s.length() > 0) {
				// set the attribute of the normal words
				//m_doc.setCharacterAttributes(lineStart, s.length(), normalAttr,
						//true);
				/*
				 * If the whole line is all the comments, the comments will be
				 * set the color yellow. If not, the string will be split into
				 * two strings, the string after ";"is the comment.
				 */
				if (s.charAt(0) == ';') {//the whole line is all the comments

					m_doc.setCharacterAttributes(lineStart, s.length(),
							commentAttr, true);
				} else {
					// spit the string, the string after the ";" is the comment
					String[] str = s.split(";");

					InputStringTokenizer st = new InputStringTokenizer(str[0]);

                    String token;
					while (st.hasMoreTokens()) {
						token = st.nextToken();

						if (token == null)
							return;

						// these two loops is used for matching the input and keywords
						for (i = 0; i < keyWord1.length; i++) {
							if (token.equals(keyWord1[i])) {
								xStart = st.getCurrPosition();
								m_doc.setCharacterAttributes(lineStart + xStart, token
										.length(), keyAttr, true);
							}
						}
						for (int j = 0; j < keyword2.length; j++) {
							if (token.equals(keyword2[j])) {
								xStart = st.getCurrPosition();

								m_doc.setCharacterAttributes(lineStart + xStart, token
										.length(), keyAttr1, true);
							}
						}

					}
					if (str.length >= 2) {
						
						
						// set the attributes of comment
						m_doc.setCharacterAttributes(lineStart + str[0].length(),
								s.length()-str[0].length(), commentAttr, true);
					}
				}
			}
			k++;
            }
            
            
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
	}
	/**
	 * This method is used for highlight the line a. i.e. change the background
	 * of that line
	 * 
	 * @param int the line number
	 */
	
	public void highLight(int a) {
		m_doc.setCharacterAttributes(enterOffset.get(a - 1), enterOffset.get(a)
				- enterOffset.get(a - 1), lightAttr, true);
	}

	/**
	 * This method is used for remove highlight of the line a. i.e. change the
	 * background of that line back
	 * 
	 * @param int the line number
	 */

	public void removeHighLight(int b) {
		m_doc.setCharacterAttributes(enterOffset.get(b - 1), enterOffset.get(b)
				- enterOffset.get(b - 1), removeLightAttr, true);
	}
	
	
  
	public void keyPressed(KeyEvent arg0) {
		textChange=true;
	}

	public void keyReleased(KeyEvent arg0) {
		textChange=true;
		// if the user input something, the program will analyze the input.
		syntaxParse();
	}
    //if the key has been pressed,set the textChange true
	public void keyTyped(KeyEvent arg0) {
		textChange=true;

	}
	/**
	 * This method returns the boolean value whether the text changed or not
	 * @return boolean*/
	public boolean getTextChange(){return this.textChange;}
}

/**
 * This class is used for analyzing the String and get the position
 */

class InputStringTokenizer extends StringTokenizer {
	String sval = " ";
	String oldStr, str;
	int m_currPosition = 0, m_beginPosition = 0;

	/**
	 * This is the constructor of the class
	 */
	public InputStringTokenizer(String str) {
		super(str, " ");
		this.oldStr = str;
		this.str = str;
	}

	/**
	 * This method overwrite the method nextToken(). It returns the next token
	 * 
	 * @return String
	 */

	public String nextToken() {
		try {
			String s = super.nextToken();
			int pos = -1;
			// the condition that input one String
			if (oldStr.equals(s)) {
				return s;
			}
			// the condition that input more then one Strings
			pos = str.indexOf(s + sval);//the start position of s
			
			if (pos == -1) {//there is no space after the current token
				pos = str.indexOf(sval + s);//find space before token
				// no input
				if (pos == -1)//cannot find space before token
					return null;
				// input the keywords but no space enter after enter the keyword
				else
					pos += 1;//the position of current space
			}

			int xBegin = pos + s.length();// the position the next word start
			str = str.substring(xBegin);//return the whole string after xBegin

			m_currPosition = m_beginPosition + pos;// the position the token
			// begin

			m_beginPosition = m_beginPosition + xBegin;
			// the position will store the next input
			return s;
		} catch (java.util.NoSuchElementException ex) {
			ex.printStackTrace();
			return null;
		}
	}

	/**
	 * This method returns the position of the token i.e. the position the last
	 * String begins
	 * 
	 * @return int
	 */

	public int getCurrPosition() {
		return m_currPosition;
	}

}
